using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using NBiDi.Extensions;
using SilverlightToolbox.RTL;

namespace SilverlightToolbox
{
    public class RTLExtender : ControlExtenderBase
    {
        public RTLExtender()
            : base()
        {
            this.BaseLoaded += new EventHandler(RTLExtender_Loaded);
        }

        void RTLExtender_Loaded(object sender, EventArgs e)
        {
            if (this.Enabled)
            {
                if (!HasCanvasesOrTextBlocks)
                {
                    SearchEntireXAMLFileForTextBlcoksAndRTLThem();
                }
                else
                {
                    if (HasCanvases)
                    {
                        SearchTargetNonExcludedCanvasesForTextBlocksAndRTLThem();
                    }
                    if (HasTextBlocks)
                    {
                        SearchNonExcludedTargetTextBlocksAndRTLThem();
                    }
                }
            }
        }

        private void SearchEntireXAMLFileForTextBlcoksAndRTLThem()
        {
            List<TextBlock> textBlocksOnCanvas = new List<TextBlock>();
            IterateOverCanvasAndFindTextBlocksToRTL(this.RootCanvas, textBlocksOnCanvas);
            foreach (TextBlock textBlockOnCanvas in textBlocksOnCanvas)
                if (!ExcludedTextBlocks.Contains(textBlockOnCanvas))
                    ChangeTextBlockToRTL(textBlockOnCanvas as TextBlock, false);
        }

        private void SearchNonExcludedTargetTextBlocksAndRTLThem()
        {
            foreach (TextBlock targetTextBlock in TargetTextBlocks)
            {
                if (!ExcludedTextBlocks.Contains(targetTextBlock))
                    ChangeTextBlockToRTL(targetTextBlock, false);
            }
        }

        private void SearchTargetNonExcludedCanvasesForTextBlocksAndRTLThem()
        {
            List<TextBlock> textBlocksOnCanvas = new List<TextBlock>();
            foreach (Canvas targetCanvas in TargetCanvases)
            {
                if (!ExcludedCanvases.Contains(targetCanvas))
                    IterateOverCanvasAndFindTextBlocksToRTL(targetCanvas, textBlocksOnCanvas);
            }
            foreach (TextBlock textBlockOnCanvas in textBlocksOnCanvas)
                if (!ExcludedTextBlocks.Contains(textBlockOnCanvas))
                    ChangeTextBlockToRTL(textBlockOnCanvas as TextBlock, false);
        }

        private static Dictionary<TextBlock, List<TextBlock>> _RTLTextBlockForNonRTLTextBlock =
            new Dictionary<TextBlock, List<TextBlock>>();
        public static Dictionary<TextBlock, List<TextBlock>> RTLTextBlockForNonRTLTextBlock
        {
            get { return RTLExtender._RTLTextBlockForNonRTLTextBlock; }
            set { RTLExtender._RTLTextBlockForNonRTLTextBlock = value; }
        }

        private static List<TextBlock> textBlocksAllreadyRTLed = new List<TextBlock>();
        private void ChangeTextBlockToRTL(TextBlock textBlockToRTL, bool Force)
        {
            if (string.IsNullOrEmpty(textBlockToRTL.Text))
                return;

            if ((CheckTextBlockNotAllreadyDone(textBlockToRTL)
                 && TextBlockDoesNotHaveAnyAnimations(textBlockToRTL, Force)
                 && TextBlockDoesNotHaveRotateTransform(textBlockToRTL, Force))
                || Force)
                DoRTLOnTextBlock(textBlockToRTL);

        }

        private bool TextBlockDoesNotHaveRotateTransform(TextBlock textBlockToRTL, bool force)
        {
            RotateTransform rotate = null;

            if (textBlockToRTL.RenderTransform is RotateTransform)
                rotate = textBlockToRTL.RenderTransform as RotateTransform;

            if (textBlockToRTL.RenderTransform is TransformGroup)
                foreach (Transform curTransformToCheckIsRotateTransformAndCheckItsAngle in ((TransformGroup)textBlockToRTL.RenderTransform).Children)
                    if (curTransformToCheckIsRotateTransformAndCheckItsAngle is RotateTransform)
                        rotate = curTransformToCheckIsRotateTransformAndCheckItsAngle as RotateTransform;
            if (rotate != null)
                if (rotate.Angle != 0.0)
                    ThrowException(force, GetRotateAngleErrorMessage(textBlockToRTL, rotate));

            return true;
        }

        private string GetRotateAngleErrorMessage(TextBlock textBlockToRTL, RotateTransform rotateThatIsNotZeroAngle)
        {
            return
                string.Format("TextBlock {0} should not have a RotateTransform angle other then 0 becuase it interfers with the RTLExtender. Currently it has an RotateTransform angle of {1} Please set it back to 0 and use a Loaded Animation to rotate the TextBlock's Canvas", textBlockToRTL.Name, rotateThatIsNotZeroAngle.Angle.ToString());
        }

        private bool TextBlockDoesNotHaveAnyAnimations(TextBlock textBlockToRTL, bool Force)
        {
            List<string> AnimationsNameOnTextBlock = new List<string>();
            IterateOverCanvasAndCheckIfHasAnimatiosOnTextBlock(textBlockToRTL, AnimationsNameOnTextBlock, this.RootCanvas);

            if (AnimationsNameOnTextBlock.Count != 0)
                ThrowException(Force, GetErrorMessageForAnimbationAndTextBlockToRTL(AnimationsNameOnTextBlock, textBlockToRTL));

            return true;
        }

        private void ThrowException(bool ForceNotThrowException, string errorMsg)
        {
            if (ForceNotThrowException)
                System.Diagnostics.Debug.WriteLine(errorMsg);
            else
                throw new SilverlightToolboxException(errorMsg);

        }


        private string GetErrorMessageForAnimbationAndTextBlockToRTL(List<string> animationsThatThrowErrorOn, TextBlock textBlockToRTL)
        {
            string AnimationsName = string.Empty;
            foreach (string curAnimationToAddToError in animationsThatThrowErrorOn)
            {
                if (!string.IsNullOrEmpty(curAnimationToAddToError))
                    AnimationsName += curAnimationToAddToError + " ,";
            }
            if (string.IsNullOrEmpty(AnimationsName))
                AnimationsName = "Unknown";

            return
                string.Format("TextBlock {0} should not have any animations on it because it will be replaced by the RTLExtender. Animations names: {1}. Please place the TextBlock in a canvas (Right Click --> Group --> Canvas) and point all animations to the Canvas.", textBlockToRTL.Name, AnimationsName);
        }


        private void IterateOverCanvasAndCheckIfHasAnimatiosOnTextBlock(TextBlock textBlockToRTL, List<string> AnimationsNameOnTextBlock, UIElement objectToIterateOver)
        {
            IterateOverResourcesAndTriggersAndfindAnimations(textBlockToRTL, AnimationsNameOnTextBlock, objectToIterateOver);

            IterateOverCanvasChildren(textBlockToRTL, AnimationsNameOnTextBlock, objectToIterateOver);
        }

        private void IterateOverCanvasChildren(TextBlock textBlockToRTL, List<string> AnimationsNameOnTextBlock, UIElement objectToIterateOver)
        {
            Canvas childCanvasToIterateOver = objectToIterateOver as Canvas;
            if (childCanvasToIterateOver != null)
            {
                foreach (Visual visualToIterateOVer in childCanvasToIterateOver.Children)
                    if (visualToIterateOVer is UIElement)
                        IterateOverCanvasAndCheckIfHasAnimatiosOnTextBlock(textBlockToRTL, AnimationsNameOnTextBlock,
                                                                           (UIElement)visualToIterateOVer);

            }
        }

        private static void IterateOverResourcesAndTriggersAndfindAnimations(TextBlock textBlockToRTL, List<string> AnimationsNameOnTextBlock, UIElement objectToIterateOver)
        {
            foreach (DependencyObject curObjectToCheckNotSetOnTextBlock in objectToIterateOver.Resources)
            {
                CheckForAnimatiosAndStoryBoard(textBlockToRTL, AnimationsNameOnTextBlock, curObjectToCheckNotSetOnTextBlock);
            }
            foreach (EventTrigger curObjectToCheckNotSetOnTextBlock in objectToIterateOver.Triggers)
            {
                foreach (BeginStoryboard beginStoryboard in curObjectToCheckNotSetOnTextBlock.Actions)
                {
                    CheckForAnimatiosAndStoryBoard(textBlockToRTL, AnimationsNameOnTextBlock, beginStoryboard.Storyboard);
                }
            }
        }

        private static void CheckForAnimatiosAndStoryBoard(TextBlock textBlockToRTL, List<string> AnimationsNameOnTextBlock, DependencyObject curObjectToCheckNotSetOnTextBlock)
        {
            Animation curAnimation = curObjectToCheckNotSetOnTextBlock as Animation;
            if (curAnimation != null)
            {
                if (curAnimation.GetValue(Storyboard.TargetNameProperty).ToString() == textBlockToRTL.Name)
                    AnimationsNameOnTextBlock.Add(curAnimation.Name);
            }

            Storyboard curStoryboard = curObjectToCheckNotSetOnTextBlock as Storyboard;
            if (curStoryboard != null)
            {
                foreach (Animation timelineToCheckChildren in curStoryboard.Children)
                {
                    if (timelineToCheckChildren.GetValue(Storyboard.TargetNameProperty).ToString() == textBlockToRTL.Name)
                    {
                        AnimationsNameOnTextBlock.Add(timelineToCheckChildren.Name);
                        if (!AnimationsNameOnTextBlock.Contains(curStoryboard.Name))
                            AnimationsNameOnTextBlock.Add(curStoryboard.Name);
                    }
                }
            }
        }

        private void DoRTLOnTextBlock(TextBlock textBlockToRTL)
        {
            if (textBlockToRTL.TextWrapping == TextWrapping.NoWrap)
            {
                //TextBlock newTextBlock = SilverlightExtensions.Clone<TextBlock>(textBlockToRTL);
                textBlockToRTL.Text = NBidi.NBidi.LogicalToVisual(textBlockToRTL.Text);

                //Canvas textBlockParent = (Canvas) textBlockToRTL.Parent;
                //textBlockParent.Children.Remove(textBlockToRTL);
                RTLTextBlockForNonRTLTextBlock[textBlockToRTL] = new List<TextBlock>(new TextBlock[] { textBlockToRTL });

            }
            else // (textBlockToRTL.TextWrapping != TextWrapping.NoWrap)
            {
                List<TextBlock> TextBlocksAttachedToTextBlockParent = SilverlightAlignmenetParagraphsToRowsAlgorithem.RtlTextBlockToWrappingScenarios(textBlockToRTL, ForceAllParagraphsAsRTL);
                foreach (TextBlock textBlocksAttachedToCanvas in TextBlocksAttachedToTextBlockParent)
                {
                    textBlocksAllreadyRTLed.Add(textBlocksAttachedToCanvas);
                }
                RTLTextBlockForNonRTLTextBlock[textBlockToRTL] = TextBlocksAttachedToTextBlockParent;
            }
        }

        private static bool CheckTextBlockNotAllreadyDone(TextBlock textBlockToRTL)
        {
            if (!textBlocksAllreadyRTLed.Contains(textBlockToRTL))
            {
                textBlocksAllreadyRTLed.Add(textBlockToRTL);
                return true;
            }
            return false;

        }


        public void RTLCanvas(Canvas canvasToRTL)
        {
            RTLCanvas(canvasToRTL, ForceAllParagraphsAsRTL);
        }

        public void RTLCanvas(Canvas canvasToRTL, bool forceRTL)
        {
            List<TextBlock> textBlocksOnCanvas = new List<TextBlock>();
            IterateOverCanvasAndFindTextBlocksToRTL(canvasToRTL, textBlocksOnCanvas);
            foreach (TextBlock textBlockOnCanvas in textBlocksOnCanvas)
                ChangeTextBlockToRTL(textBlockOnCanvas, forceRTL);

        }

        public void RTLTextBlock(TextBlock textBlockToRTL)
        {
            RTLTextBlock(textBlockToRTL, ForceAllParagraphsAsRTL);
        }

        public void RTLTextBlock(TextBlock textBlockToRTL, bool forceRTL)
        {
            ChangeTextBlockToRTL(textBlockToRTL, forceRTL);
        }

        private void IterateOverCanvasAndFindTextBlocksToRTL(Canvas canvasToIterateOver, List<TextBlock> foundTextBlocks)
        {
            foreach (Visual curChildToCheckIfTextBlockOrCanvas in canvasToIterateOver.Children)
            {
                if (curChildToCheckIfTextBlockOrCanvas is TextBlock)
                    if (!ExcludedTextBlocks.Contains((TextBlock)curChildToCheckIfTextBlockOrCanvas))
                        foundTextBlocks.Add(curChildToCheckIfTextBlockOrCanvas as TextBlock);
                if (curChildToCheckIfTextBlockOrCanvas is Canvas)
                    if (!ExcludedCanvases.Contains((Canvas)curChildToCheckIfTextBlockOrCanvas))
                        IterateOverCanvasAndFindTextBlocksToRTL(curChildToCheckIfTextBlockOrCanvas as Canvas, foundTextBlocks);
            }
        }

        private bool HasCanvasesOrTextBlocks
        {
            get
            {
                return HasCanvases || HasTextBlocks;
            }
        }

        private bool HasTextBlocks
        {
            get
            {
                return TargetTextBlocks.Count != 0;
            }
        }

        private bool HasCanvases
        {
            get
            {
                return TargetCanvases.Count != 0;
            }
        }

        private List<Canvas> _TargetCanvases = new List<Canvas>();
        [FillXamlElementssByName("TargetCanvasesName")]
        internal List<Canvas> TargetCanvases
        {
            get { return _TargetCanvases; }
            set { _TargetCanvases = value; }
        }


        public string TargetCanvasesName
        {
            get;
            set;
        }

        private List<TextBlock> _TargetTextBlocks = new List<TextBlock>();
        [FillXamlElementssByName("TargetTextBlocksName")]
        internal List<TextBlock> TargetTextBlocks
        {
            get { return _TargetTextBlocks; }
            set { _TargetTextBlocks = value; }
        }

        public string TargetTextBlocksName
        {
            get;
            set;
        }

        private List<Canvas> _ExcludedCanvases = new List<Canvas>();
        [FillXamlElementssByName("ExcludedCanvasesName")]
        internal List<Canvas> ExcludedCanvases
        {
            get { return _ExcludedCanvases; }
            set { _ExcludedCanvases = value; }
        }

        public string ExcludedCanvasesName
        {
            get;
            set;
        }

        private List<TextBlock> _ExcludedTextBlocks = new List<TextBlock>();
        [FillXamlElementssByName("ExcludedTextBlocksName")]
        internal List<TextBlock> ExcludedTextBlocks
        {
            get { return _ExcludedTextBlocks; }
            set { _ExcludedTextBlocks = value; }
        }

        public string ExcludedTextBlocksName
        {
            get;
            set;
        }

        public bool ForceAllParagraphsAsRTL
        {
            get;
            set;
        }
    }
}